package micro86.engine;

import micro86.engine.parametres.*;
import micro86.operation.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * (c) Kate Luzhevskaya 2013
 */
public class Parser {
    private Map<String, Integer> labelMap = new HashMap<String, Integer>(); //label map
    List<IOperation> operList = new ArrayList<IOperation>();
    private int index;

    private Integer getLabelByName(String name) {
        return labelMap.get(name);
    }

    private void setLabelMap(String name, int index) {
        labelMap.put(name, index);
    }

    public void setIndex(int value) {
        this.index = value;
    }

    public void incIndex() {
        ++this.index;
    }

    public List<IOperation> getOperList() {
        return operList;
    }

    private IntParameter parseIntParam(String word) throws ParseException {
        if (word.equals("AX")) {
            return new RegAX();
        }
        else if (word.equals("BX")) {
            return new RegBX();
        }
        else if (word.equals("CX")) {
            return new RegCX();
        }
        else {
            try {
                return new IntConst(Integer.parseInt(word));
            } catch(NumberFormatException e) {
                throw new ParseException("Invalid parameter");
            }
        }
    }

    private IntParameter parseRegister(String word) throws ParseException {
        if (word.equals("AX")) {
            return new RegAX();
        }
        else if (word.equals("BX")) {
            return new RegBX();
        }
        else if (word.equals("CX")) {
            return new RegCX();
        }
        else {
            throw new ParseException("Invalid parameter");
        }
    }

    private static QuotedString parseQuotedStr(String word) throws ParseException {
        if (Utils.isQuotedString(word)) {
            return new QuotedString(word);
        }
        else {
            throw new ParseException("Invalid parameter");
        }
    }

    private static IntConst parseMemoryAddress(String word) throws ParseException {
        if (Utils.isMemoryAddress(word)) {
            return new IntConst(Integer.parseInt(word.substring(1, word.length() - 2)));
        }
        else {
            throw new ParseException("Invalid parameter");
        }
    }


    private IOperation parseMOVCommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 3) {
            throw new ParseException("MOV requires 2 operands, found: " + (len - 1) );
        }
        else {
            IntParameter param1, param2;
            if (Utils.isRegister(wordArr[1])){
                try {
                    param1 = parseRegister(wordArr[1]);
                } catch (ParseException e) {
                    throw new ParseException("Invalid parameter at index: 1");
                }

                if (Utils.isNumeric(wordArr[2]) || Utils.isRegister(wordArr[2])){
                    try {
                        param2 = parseIntParam(wordArr[2]);
                        return new Mov(param1, param2);
                    } catch (ParseException e) {
                        throw new ParseException("Invalid parameter at index: 2");
                    }
                }

                else if(Utils.isMemoryAddress(wordArr[2])){
                    try {
                        param2 = parseMemoryAddress(wordArr[2]);
                        return new MovFromMemory(param1, param2);
                    } catch (ParseException e) {
                        throw new ParseException("Invalid parameter at index: 2");
                    }
                }

                else {
                    throw new ParseException("Invalid parameter at index: 2");
                }
            }
            else if (Utils.isMemoryAddress(wordArr[1])) {
                try {
                    param1 = parseMemoryAddress(wordArr[1]);
                } catch (ParseException e) {
                    throw new ParseException("Invalid parameter at index: 1");
                }
                try {
                    param2 = parseIntParam(wordArr[2]);
                    return new MovToMemory(param1, param2);
                } catch (ParseException e) {
                    throw new ParseException("Invalid parameter at index: 2");
                }
            }

            else {
                throw new ParseException("Invalid parameter at index: 1");
            }
        }
    }

    private Add parseADDCommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 3) {
            throw new ParseException("ADD requires 2 operands, found: " + (len - 1) );
        }
        else {
            IntParameter param1, param2;
            try {
                param1 = parseRegister(wordArr[1]);
            } catch (ParseException e) {
                throw new ParseException("Invalid parameter at index: 1");
            }
            try {
                param2 = parseIntParam(wordArr[2]);
            } catch (ParseException e) {
                throw new ParseException("Invalid parameter at index: 2");
            }

            return new Add(param1, param2);
        }
    }

    private Sub parseSUBCommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 3) {
            throw new ParseException("SUB requires 2 operands, found: " + (len - 1) );
        }
        else {
            IntParameter param1, param2;
            try {
                param1 = parseRegister(wordArr[1]);
            } catch (ParseException e) {
                throw new ParseException("Invalid parameter at index: 1");
            }
            try {
                param2 = parseIntParam(wordArr[2]);
            } catch (ParseException e) {
                throw new ParseException("Invalid parameter at index: 2");
            }

            return new Sub(param1, param2);
        }
    }

    private IntMul parseMULCommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 3) {
            throw new ParseException("MUL requires 2 operands, found: " + (len - 1) );
        }
        else {
            IntParameter param1, param2;
            try {
                param1 = parseRegister(wordArr[1]);
            } catch (ParseException e) {
                throw new ParseException("Invalid parameter at index: 1");
            }
            try {
                param2 = parseIntParam(wordArr[2]);
            } catch (ParseException e) {
                throw new ParseException("Invalid parameter at index: 2");
            }

            return new IntMul(param1, param2);
        }
    }

    private IntDiv parseDIVCommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 3) {
            throw new ParseException("DIV requires 2 operands, found: " + (len - 1) );
        }
        else {
            IntParameter param1, param2;
            try {
                param1 = parseRegister(wordArr[1]);
            } catch (ParseException e) {
                throw new ParseException("Invalid parameter at index: 1");
            }
            try {
                param2 = parseIntParam(wordArr[2]);
            } catch (ParseException e) {
                throw new ParseException("Invalid parameter at index: 2");
            }

            return new IntDiv(param1, param2);
        }
    }

    private Print parsePRNCommand(String line) throws ParseException {
        List<IOperation> prnArray = new ArrayList<IOperation>();
        String wordArr[] = Utils.getWordArray(line);
        PrintIntParam prnInt;
        PrintQuotedString prnStr;
        PrintEmpty prnEmpty;
        int len = wordArr.length;

        if (len > 1) {
            for (int i = 1; i < len; i++) {
                String cur_word = wordArr[i];
                if (Utils.isRegister(cur_word) || Utils.isNumeric(cur_word)) {
                    IntParameter param = parseIntParam(cur_word);
                    prnInt = new PrintIntParam(param);
                    prnArray.add(prnInt);
                }
                else if (Utils.isQuotedString(cur_word)) {
                    QuotedString str = parseQuotedStr(cur_word);
                    prnStr = new PrintQuotedString(str);
                    prnArray.add(prnStr);
                }

                else {
                    throw new ParseException("Invalid PRN command: word " + (i+1) + " doesn't match correct command.");
                }
            }
        }
        else {
            prnEmpty = new PrintEmpty();
            prnArray.add(prnEmpty);
        }
        return new Print(prnArray);

    }

    private Cmr parseCMRCommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 3) {
            throw new ParseException("CMR requires 2 operands, found: " + (len - 1) );
        }
        else {
            IntParameter param1, param2;
            try {
                param1 = parseIntParam(wordArr[1]);
            } catch (ParseException e) {
                throw new ParseException("Invalid parameter at index: 1");
            }
            try {
                param2 = parseIntParam(wordArr[2]);
            } catch (ParseException e) {
                throw new ParseException("Invalid parameter at index: 2");
            }

            return new Cmr(param1, param2);
        }
    }

    private void parseLabel(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 1) {
            throw new ParseException("Label doesn't require operands, found: " + (len - 1) );
        }
        else {
            String labelName = wordArr[0].substring(1);
            Integer labelIndex = getLabelByName(labelName);
            if (labelIndex == null) {
                setLabelMap(labelName, index);
                index--;
            }
            else {
                throw new ParseException("More than one label " + labelName);
            }
        }
    }

    private Jmp parseJMPCommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 2) {
            throw new ParseException("JMP requires 1 operand, found: " + (len - 1) );
        }

        else {
            String labelName = wordArr[1];
            Integer labelIndex = getLabelByName(labelName);
            if (labelIndex != null) {
               return new Jmp(labelIndex);
            }
            else {
                throw new ParseException("Label " + labelName + " not found.");
            }
        }
    }

    private Jz parseJZCommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 2) {
            throw new ParseException("JMP requires 1 operand, found: " + (len - 1) );
        }

        else {
            String labelName = wordArr[1];
            Integer labelIndex = getLabelByName(labelName);
            if (labelIndex != null) {
                return new Jz(labelIndex);
            }
            else {
                throw new ParseException("Label " + labelName + " not found.");
            }
        }
    }

    private Je parseJECommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 2) {
            throw new ParseException("JMP requires 1 operand, found: " + (len - 1) );
        }

        else {
            String labelName = wordArr[1];
            Integer labelIndex = getLabelByName(labelName);
            if (labelIndex != null) {
                return new Je(labelIndex);
            }
            else {
                throw new ParseException("Label " + labelName + " not found.");
            }
        }
    }

    private Jne parseJNECommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 2) {
            throw new ParseException("JMP requires 1 operand, found: " + (len - 1) );
        }

        else {
            String labelName = wordArr[1];
            Integer labelIndex = getLabelByName(labelName);
            if (labelIndex != null) {
                return new Jne(labelIndex);
            }
            else {
                throw new ParseException("Label " + labelName + " not found.");
            }
        }
    }

    private Jnz parseJNZCommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 2) {
            throw new ParseException("JMP requires 1 operand, found: " + (len - 1) );
        }

        else {
            String labelName = wordArr[1];
            Integer labelIndex = getLabelByName(labelName);
            if (labelIndex != null) {
                return new Jnz(labelIndex);
            }
            else {
                throw new ParseException("Label " + labelName + " not found.");
            }
        }
    }

    private Jl parseJLCommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 2) {
            throw new ParseException("JMP requires 1 operand, found: " + (len - 1) );
        }

        else {
            String labelName = wordArr[1];
            Integer labelIndex = getLabelByName(labelName);
            if (labelIndex != null) {
                return new Jl(labelIndex);
            }
            else {
                throw new ParseException("Label " + labelName + " not found.");
            }
        }
    }

    private Jg parseJGCommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 2) {
            throw new ParseException("JMP requires 1 operand, found: " + (len - 1) );
        }

        else {
            String labelName = wordArr[1];
            Integer labelIndex = getLabelByName(labelName);
            if (labelIndex != null) {
                return new Jg(labelIndex);
            }
            else {
                throw new ParseException("Label " + labelName + " not found.");
            }
        }
    }

    private Jle parseJLECommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 2) {
            throw new ParseException("JMP requires 1 operand, found: " + (len - 1) );
        }

        else {
            String labelName = wordArr[1];
            Integer labelIndex = getLabelByName(labelName);
            if (labelIndex != null) {
                return new Jle(labelIndex);
            }
            else {
                throw new ParseException("Label " + labelName + " not found.");
            }
        }
    }

    private Jge parseJGECommand(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        int len = wordArr.length;

        if (len != 2) {
            throw new ParseException("JMP requires 1 operand, found: " + (len - 1) );
        }

        else {
            String labelName = wordArr[1];
            Integer labelIndex = getLabelByName(labelName);
            if (labelIndex != null) {
                return new Jge(labelIndex);
            }
            else {
                throw new ParseException("Label " + labelName + " not found.");
            }
        }
    }

    public void parse(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        if (wordArr.length == 0) {
            throw new ParseException("Empty line.");
        }

        else if (wordArr[0].equals("MOV")) {
            operList.add(index, parseMOVCommand(line));
        }

        else if (wordArr[0].equals("ADD")) {
            operList.add(index, parseADDCommand(line));
        }

        else if (wordArr[0].equals("SUB")) {
            operList.add(index, parseSUBCommand(line));
        }

        else if (wordArr[0].equals("MUL")) {
            operList.add(index, parseMULCommand(line));
        }

        else if (wordArr[0].equals("DIV")) {
            operList.add(index, parseDIVCommand(line));
        }

        else if (wordArr[0].equals("PRN")) {
            operList.add(index, parsePRNCommand(line));
        }
        else if (wordArr[0].equals("CMP")) {
            operList.add(index, parseCMRCommand(line));
        }
        else if (Utils.isLabel(wordArr[0])) {
            parseLabel(line);
        }
        else if ((wordArr[0].equals("JMP"))) {
            operList.add(index, null);
        }
        else if ((wordArr[0].equals("JZ"))) {
            operList.add(index, null);
        }
        else if ((wordArr[0].equals("JE"))) {
            operList.add(index, null);
        }
        else if ((wordArr[0].equals("JNE"))) {
            operList.add(index, null);
        }
        else if ((wordArr[0].equals("JNZ"))) {
            operList.add(index, null);
        }
        else if ((wordArr[0].equals("JL"))) {
            operList.add(index, null);
        }
        else if ((wordArr[0].equals("JG"))) {
            operList.add(index, null);
        }
        else if ((wordArr[0].equals("JLE"))) {
            operList.add(index, null);
        }
        else if ((wordArr[0].equals("JGE"))) {
            operList.add(index, null);
        }
        else {
            throw new ParseException("First word doesn't match correct command.");
        }
    }

    public void finishParse(String line) throws ParseException {
        String wordArr[] = Utils.getWordArray(line);
        if (Utils.isLabel(wordArr[0])) {
            index--;
        }
        else if ((wordArr[0].equals("JMP"))) {
            operList.set(index, parseJMPCommand(line));
        }
        else if ((wordArr[0].equals("JZ"))) {
            operList.set(index, parseJZCommand(line));
        }
        else if ((wordArr[0].equals("JE"))) {
            operList.set(index, parseJECommand(line));
        }
        else if ((wordArr[0].equals("JNE"))) {
            operList.set(index, parseJNECommand(line));
        }
        else if ((wordArr[0].equals("JNZ"))) {
            operList.set(index, parseJNZCommand(line));
        }
        else if ((wordArr[0].equals("JL"))) {
            operList.set(index, parseJLCommand(line));
        }
        else if ((wordArr[0].equals("JG"))) {
            operList.set(index, parseJGCommand(line));
        }
        else if ((wordArr[0].equals("JLE"))) {
            operList.set(index, parseJLECommand(line));
        }
        else if ((wordArr[0].equals("JGE"))) {
            operList.set(index, parseJGECommand(line));
        }
    }

}
